package net.qiqbframework.modelling.domain;

import lombok.extern.slf4j.Slf4j;
import net.qiqbframework.commandhandling.ApplyMore;
import net.qiqbframework.commandhandling.CommandMessage;
import net.qiqbframework.commandhandling.inspection.AnnotatedCommandHandlerInterceptor;
import net.qiqbframework.config.ConfigurationException;
import net.qiqbframework.eventhandling.EventMessage;
import net.qiqbframework.eventhandling.GenericDomainEventMessage;
import net.qiqbframework.eventhandling.GenericEventMessage;
import net.qiqbframework.eventsourcing.EventStorageEngine;
import net.qiqbframework.messaging.DefaultInterceptorChain;
import net.qiqbframework.messaging.Message;
import net.qiqbframework.messaging.MetaData;
import net.qiqbframework.messaging.annotation.MessageExecutableHandler;
import net.qiqbframework.messaging.task.CurrentMessageTask;
import net.qiqbframework.messaging.task.MessageTask;

import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import java.util.stream.Collectors;


/**
 *
 */
@Slf4j
public class AnnotatedAggregate<AR> extends AggregateLifecycle implements Aggregate<AR>, ApplyMore {

    protected final AggregateModel<AR> inspector;

    private final Queue<Runnable> delayedTasks = new LinkedList<>();

    private final EventStorageEngine eventStorageEngine;


    /**
     * 事件聚合根对象
     */
    protected AR aggregateRoot;


    private boolean applying = false;

    private boolean executingDelayedTasks = false;
    /**
     * 删除标识
     */
    private boolean isDeleted = false;
    /**
     * 是否是新的
     */
    private boolean isNew = false;

    private Long lastKnownSequence = 0L;

    public void initSequence(long lastKnownSequenceNumber) {
        this.lastKnownSequence = lastKnownSequenceNumber;
    }

    protected AnnotatedAggregate(AR aggregateRoot, AggregateModel<AR> model, EventStorageEngine eventStorageEngine) {
        this.inspector = model;
        this.aggregateRoot = aggregateRoot;
        this.eventStorageEngine = eventStorageEngine;
    }


    protected AnnotatedAggregate(AggregateModel<AR> inspector, EventStorageEngine eventStorageEngine) {
        this.inspector = inspector;
        this.eventStorageEngine = eventStorageEngine;
    }


    protected void registerRoot(Callable<AR> aggregateFactory, boolean isNew) throws Exception {
        this.aggregateRoot = executeWithResult(aggregateFactory);

        this.isNew = isNew;

        execute(() -> {
            while (!delayedTasks.isEmpty()) {
                delayedTasks.remove().run();
            }
        });
    }

    @Override
    public String type() {
        return inspector.type();
    }

    @Override
    public AnnotatedBizIdentifier<?> aggregateRootId() {
        final BizIdentifierModel<?> businessIdModel = inspector.getEntityIdModel();
        final Object aggregateRootId = businessIdModel.bizIdentifier(this);
        return new AnnotatedBizIdentifier(businessIdModel, aggregateRootId);
    }

    @Override
    public Object identifier() {
        return inspector.getIdentifier(aggregateRoot);
    }

    @Override
    public Long version() {
        return inspector.getVersion(aggregateRoot);
    }

    @Override
    public AR aggregateRoot() {
        return aggregateRoot;
    }


    @Override
    protected boolean getIsLive() {
        return true;
    }

    @Override
    public boolean isDeleted() {
        return isDeleted;
    }

    @Override
    public boolean isNew() {
        return isNew;
    }

    @SuppressWarnings("unchecked")
    @Override
    public Class<? extends AR> rootType() {
        return (Class<? extends AR>) aggregateRoot.getClass();
    }

    @Override
    protected void doMarkDeleted() {
        this.isDeleted = true;
    }

    @Override
    protected void doMarkNew() {
        this.isNew = true;
    }

    @Override
    protected void doMarkNotNew() {
        this.isNew = false;
    }


    @Override
    public Object handle(Message<?> message) throws Exception {
        Callable<Object> messageHandling;

        if (message instanceof CommandMessage) {
            messageHandling = () -> doHandle((CommandMessage<?>) message);
        } else {
            throw new IllegalArgumentException("Unsupported message type: " + message.getClass());
        }

        return executeWithResult(messageHandling);
    }

    private Object doHandle(CommandMessage<?> commandMessage) throws Exception {
        // noinspection unchecked
        List<AnnotatedCommandHandlerInterceptor<? super AR>> interceptors =
                inspector.commandHandlerInterceptors((Class<? extends AR>) aggregateRoot.getClass())
                        .map(chi -> new AnnotatedCommandHandlerInterceptor<>(chi, aggregateRoot))
                        .collect(Collectors.toList());

        // noinspection unchecked
        List<MessageExecutableHandler> potentialHandlers =
                inspector.commandHandlers((Class<? extends AR>) aggregateRoot.getClass())
                        .filter(mh -> mh.canHandle(commandMessage))
                        .collect(Collectors.toList());
        if (potentialHandlers.isEmpty()) {

            return null;
        }

        Object result;
        if (interceptors.isEmpty()) {
            result = findHandlerAndHandleCommand(potentialHandlers, commandMessage);
        } else {
            // noinspection unchecked
            result = new DefaultInterceptorChain<>(
                    (MessageTask<CommandMessage<?>>) CurrentMessageTask.get(),
                    interceptors,
                    m -> findHandlerAndHandleCommand(potentialHandlers, commandMessage)
            ).proceed();
        }
        return result;
    }

    private Object findHandlerAndHandleCommand(List<MessageExecutableHandler> handlers,
                                               CommandMessage<?> command) throws Exception {
        // noinspection unchecked
        return handlers.stream()

                .findFirst()
                .orElseThrow(() -> new ConfigurationException(
                        "Aggregate cannot handle command [" + command.getCommandName()
                                + "], as there is no entity instance within the aggregate to forward it to."
                ))
                .handle(command, aggregateRoot);
    }

    @Override
    protected <P> ApplyMore doApply(P payload, MetaData metaData) {
        if (!applying && aggregateRoot != null) {
            applying = true;
            try {
                log.info("执行方法：add doApply {}",payload);
                eventStorageEngine.appendEvents(createMessage(payload, metaData));
                // publish(createMessage(payload, metaData));
            } finally {
                applying = false;
            }
            if (!executingDelayedTasks) {
                executingDelayedTasks = true;
                try {
                    while (!delayedTasks.isEmpty()) {
                        delayedTasks.remove().run();
                    }
                } finally {
                    executingDelayedTasks = false;
                    delayedTasks.clear();
                }
            }
        } else {

            delayedTasks.add(() -> {
                log.info("执行方法：add delayedTasks {}",payload);
                doApply(payload, metaData);
            });
        }
        return this;
    }

    protected <P> EventMessage<P> createMessage(P payload, MetaData metaData) {
        if (lastKnownSequence != null) {
            String type = inspector.declaredType(rootType())
                    .orElse(rootType().getSimpleName());
            long seq = lastKnownSequence + 1;

            return new GenericDomainEventMessage<>(type, identifierAsString(), seq, payload, metaData);
        }
        return new GenericEventMessage<>(payload, metaData);
    }


    @Override
    public ApplyMore andThenApply(Supplier<?> payloadOrMessageSupplier) {
        return andThen(() -> applyMessageOrPayload(payloadOrMessageSupplier.get()));
    }

    @Override
    public ApplyMore andThen(Runnable runnable) {
        if (applying || aggregateRoot == null) {
            delayedTasks.add(runnable);
        } else {
            runnable.run();
        }
        return this;
    }


    protected void applyMessageOrPayload(Object payloadOrMessage) {
        if (payloadOrMessage instanceof Message<?> message) {
            AggregateContext.addEvent(message.getPayload(), message.getMetaData());
        } else if (payloadOrMessage != null) {
            AggregateContext.addEvent(payloadOrMessage, MetaData.emptyInstance());
        }
    }


}
